package com.wanfajie.smsdistribute.template

import android.os.Parcel
import android.os.Parcelable
import com.wanfajie.smsdistribute.db.bean.phone.PhoneNumber
import com.wanfajie.smsdistribute.db.bean.template.TemplateBean
import com.wanfajie.smsdistribute.template.convert.TemplateConvertable
import com.wanfajie.smsdistribute.template.convert.TemplateConverter

data class SmsTemplate private constructor(
        private val mRawText: CharSequence,
        private val mTagPos: List<Pair<Int, Int>>,
        private val mTagMap: Map<Pair<Int, Int>, CharSequence> = HashMap()
) : TemplateConvertable, Parcelable {

    class Builder private constructor(
            private val text: CharSequence,
            private val pointSet: MutableSet<Pair<Int, Int>>,
            private val pointMap: MutableMap<Pair<Int, Int>, CharSequence>
    ) {

        constructor(text: CharSequence) : this(text, HashSet(), HashMap())

        constructor(tempBean: TemplateBean): this(
                tempBean.content,
                tempBean.placeholders.map {
                    it.pair
                }.toMutableSet(),
                HashMap()
        ) {
            parsePlaceholderNamePos(text, pointSet, pointMap)
        }

        constructor(template: SmsTemplate) : this(
                template.mRawText,
                template.mTagPos.toMutableSet(),
                template.mTagMap.toMutableMap()
        )

        private fun checkInterweave(point: Pair<Int, Int>) {
            val list = pointSet.filterNot {
                (point.first <= it.first && point.second <= it.first)
                        || (point.first >= it.second && point.second >= it.second)
            }

            if (list.isNotEmpty())
                throw IllegalArgumentException("$point 穿插于 ${list.first()}")
        }

        fun addTag(point : Pair<Int, Int>, ignore: Boolean = true) : Builder {
            val (start, end) = point

            if (start > end)
                throw IllegalArgumentException("start > end [$start, $end]")

            if (end > text.length)
                throw StringIndexOutOfBoundsException("$end > ${text.length}")

            if (pointSet.contains(point) && !ignore)
                throw IllegalArgumentException("$point 已存在")

            if (ignore) {
                try {
                    checkInterweave(point)
                } catch (e: IllegalArgumentException) {
                    return this
                }
            } else {
                checkInterweave(point)
            }

            pointSet.add(point)
            pointMap[point] = text.substring(point.first+1, point.second)
            return this
        }

        fun addTag(start: Int, end: Int, ignore: Boolean = true) : Builder {
            return addTag(start to end, ignore)
        }

        fun addAll(collection: Collection<Pair<Int, Int>>) : Builder {
            collection.forEach { addTag(it) }
            return this
        }

        fun create(): SmsTemplate {
            val list = pointSet.sortedByDescending { it.first }
            return SmsTemplate(text, list, pointMap.toMap())
        }
    }

    val placeholders : Map<Pair<Int, Int>, CharSequence> get() = mTagMap
    val placeholderPointers : List<Pair<Int, Int>> get() = mTagPos

    fun render(obj: Any, emptyDefault: Boolean = true): String {
        val sb = StringBuilder(mRawText)

        if (obj is Map<*, *>) {
            mTagPos.forEach {
                val tag = mTagMap[it]
                val value = obj[tag]

                if (value != null) {
                    sb.replace(it.first, it.second, value.toString())
                }
            }
        } else if (obj is PhoneNumber) {
            mTagPos.forEach {
                val tag = mTagMap[it]!!
                val value = obj.get(tag)

                sb.replace(it.first, it.second, value)
            }
        }

        return sb.toString()
    }

    override fun toString(): String = mRawText.toString()

    override fun <R> convert(converter: TemplateConverter<R>): R {
        converter.setRawMaterial(mRawText)
        mTagMap.forEach {
            converter.mark(it.value, it.key)
        }

        return converter.convert()
    }

    operator fun contains(point: Pair<Int, Int>): Boolean {
        return placeholderPointers.contains(point)
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(mRawText.toString())
        val posArr = Array(mTagPos.size * 2) {
            val (s, e) = mTagPos[it/2]
            if (it % 2 == 0) s else e
        }
        parcel.writeArray(posArr)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<SmsTemplate> {
        override fun createFromParcel(parcel: Parcel): SmsTemplate {
            val text = parcel.readString()
            val intArr = parcel.readArray(Array<Int>::class.java.classLoader)

            val builder = Builder(text)
            for (i in 0 until intArr.size step 2) {
                builder.addTag(intArr[i] as Int, intArr[i+1] as Int)
            }

            return builder.create()
        }

        override fun newArray(size: Int): Array<SmsTemplate?> {
            return arrayOfNulls(size)
        }

        private fun parsePlaceholderNamePos(
                text: CharSequence,
                collection: Collection<Pair<Int, Int>>,
                destMap: MutableMap<Pair<Int, Int>, CharSequence>? = null
        ) : MutableMap<Pair<Int, Int>, CharSequence> {
            val resultMap = destMap ?: HashMap()

            collection.forEach {
                val (s, e) = it
                resultMap[it] = text.substring(s+1, e)
            }

            return resultMap
        }
    }
}
